#include <algorithm>
#include <iostream>
#include <string>
#include <vector>
#include <boost/format.hpp>
#include <boost/config/warning_disable.hpp>
#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/include/phoenix_core.hpp>
#include <boost/spirit/include/phoenix_container.hpp>
#include <boost/spirit/include/phoenix_statement.hpp>
#include <boost/spirit/include/phoenix_object.hpp>
#include <boost/spirit/include/phoenix_operator.hpp>
#include <boost/spirit/include/support_argument.hpp>
#include "gac_lang.hh"

using namespace boost::spirit;
using namespace boost::spirit::qi;
using namespace boost::spirit::ascii;

using boost::phoenix::ref;
using boost::phoenix::push_back;
using boost::phoenix::val;
using boost::phoenix::construct;


template <typename Iterator>
struct gac_lang_parser : grammar<Iterator, boost::spirit::ascii::space_type> {
  void test_fun() {}
  void test_fun2(pcode_array& code) {}
  void impl_available(pcode_array& code) {
    static pcode_array mycode = { pcode_load_obj_pos
				, pcode_dup
				, pcode_load_immediate
				, POS_CARRIED
				, pcode_cmp_eq
				, pcode_exchange
				, pcode_load_here
				, pcode_cmp_eq
				, pcode_and
    };
    std::copy(code.begin(), code.end(), back_inserter(code));
  }
  gac_lang_parser(pcode_array& code) : gac_lang_parser::base_type(expression), code(code) {
    static const pcode_array code_for_available = { pcode_load_obj_pos
						    , pcode_dup
						    , pcode_load_immediate
						    , POS_CARRIED
						    , pcode_cmp_eq
						    , pcode_exchange
						    , pcode_load_here
						    , pcode_cmp_eq
						    , pcode_or
    };
    static const pcode_array code_for_get = { pcode_load_immediate, POS_CARRIED, pcode_exchange, pcode_move_obj };
    static const pcode_array code_for_decr= { pcode_dup, pcode_load_counter, pcode_load_immediate, 1, pcode_sub, pcode_exchange, pcode_store_counter };
    static const pcode_array code_for_incr= { pcode_dup, pcode_load_counter, pcode_load_immediate, 1, pcode_add, pcode_exchange, pcode_store_counter };

    expression =
      (lit("if") > bool_exprs > lit("then")) [push_back(boost::phoenix::ref(code), pcode_end_on_false)] >> *(action)
      | +(action)
      ;
    expression.name("expression");
    
    bool_exprs = bool_expr_not
      >> *( (lit("and") > bool_expr_not [push_back(boost::phoenix::ref(code), pcode_and)])
	    | (lit("or") > bool_expr_not [push_back(boost::phoenix::ref(code), pcode_or)])
	    )
      ;
    bool_exprs.name("bool expressions");
    
    bool_expr_not = bool_expr
      | (lit("not") > bool_expr [push_back(boost::phoenix::ref(code), pcode_lnot)])
      ;
    bool_expr_not.name("bool_expr_not");

    bool_expr = cond_leftvalue | cond_visited | cond_objprobs | cond_markers;
    bool_expr.name("boolean expression");
    
    cond_leftvalue = value > (cond_eq | cond_lt | cond_gt);
    cond_leftvalue.name("cond_leftvalue");

    cond_lt = '<' > value [push_back(boost::phoenix::ref(code), pcode_cmp_lt)];
    cond_lt.name("cond_lt");
    cond_eq = '=' > value [push_back(boost::phoenix::ref(code), pcode_cmp_eq)];
    cond_eq.name("cond_eq");
    cond_gt = '>' > value [push_back(boost::phoenix::ref(code), pcode_cmp_gt)];
    cond_gt.name("cond_gt");

    cond_markers =
      (lit("set?") > number) [ push_back(boost::phoenix::ref(code), pcode_load_switch)
			       , push_back(boost::phoenix::ref(code), pcode_load_true)
			       , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
			      ]
      | (lit("reset?") > number) [ push_back(boost::phoenix::ref(code), pcode_load_switch)
				   , push_back(boost::phoenix::ref(code), pcode_load_false)
				   , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
				   ]
      ;
    cond_markers.name("cond_markers");

    cond_visited = 
      lit("visited") [push_back(boost::phoenix::ref(code), pcode_load_here)
		      , push_back(boost::phoenix::ref(code), pcode_load_visited)
		      ]
      ;
    cond_visited.name("cond_visited");

    cond_objprobs = 
      (lit("carried") > objnum) [push_back(boost::phoenix::ref(code), pcode_load_obj_pos)
			       , push_back(boost::phoenix::ref(code), pcode_load_immediate)
			       , push_back(boost::phoenix::ref(code), POS_CARRIED)
			       , push_back(boost::phoenix::ref(code), pcode_cmp_eq)
			       ]
      | (lit("ishere") > objnum) [(
				   [this]{this->code.push_back(pcode_load_obj_pos); this->code.push_back(pcode_load_here); this->code.push_back(pcode_cmp_eq); }
				   )]
      | (lit("available") > objnum) [( [&] {std::copy(code_for_available.begin(), code_for_available.end(), back_inserter(this->code));} )] ;
      ;
    cond_objprobs.name("cond_objprobs");

    objnum = number
      | lit("obj1") [push_back(boost::phoenix::ref(code), pcode_load_obj1)]
      | lit("obj2") [push_back(boost::phoenix::ref(code), pcode_load_obj2)]
      ;
    objnum.name("objnum");

    value = objnum
      | lit("here") [push_back(boost::phoenix::ref(code), pcode_load_here)]
      | (lit("posof") > objnum) [push_back(boost::phoenix::ref(code), pcode_load_obj_pos)]
      | (lit("weightof") > objnum) [push_back(boost::phoenix::ref(code), pcode_load_obj_weight)]
      ;
    value.name("value");

    number = int_ [
		    push_back(boost::phoenix::ref(code), pcode_load_immediate),
		    push_back(boost::phoenix::ref(code), _1)
		    ]
      ;
    number.name("number");

    action = 
      ( lit("long") [push_back(boost::phoenix::ref(code), pcode_look_long)] )
      | (lit("short") [push_back(boost::phoenix::ref(code), pcode_look_short)] )
      | (lit("goto") > value) [push_back(boost::phoenix::ref(code), pcode_store_here)]
      | (lit("msg") > number ) [push_back(boost::phoenix::ref(code), pcode_write_mesg)]
      | (lit("bring") > objnum ) [push_back(boost::phoenix::ref(code), pcode_load_here),
				  push_back(boost::phoenix::ref(code), pcode_exchange),
				  push_back(boost::phoenix::ref(code), pcode_move_obj)
				  ]
      | (lit("describe") > objnum ) [([this]{this->code.push_back(pcode_examine_obj);})]
      | lit("exits") [push_back(boost::phoenix::ref(code), pcode_print_exits)]
      | lit("objects") [push_back(boost::phoenix::ref(code), pcode_print_objs)]
      | lit("fin") [push_back(boost::phoenix::ref(code), pcode_finish)]
      | lit("nl") [push_back(boost::phoenix::ref(code), pcode_nl)]
      | lit("inv") [push_back(boost::phoenix::ref(code), pcode_inventory)]
      | lit("true") [push_back(boost::phoenix::ref(code), pcode_load_true)]
      | lit("false") [push_back(boost::phoenix::ref(code), pcode_load_false)]
      | (lit("get") > objnum) [( [&] {std::copy(code_for_get.begin(), code_for_get.end(), back_inserter(this->code));} )]
      | ( lit("set") > number ) [( [this]{
	  this->code.push_back(pcode_load_true);
	  this->code.push_back(pcode_exchange);
	  this->code.push_back(pcode_store_switch);
	} )]
      | ( lit("reset") > number ) [( [this]{
	  this->code.push_back(pcode_load_false);
	  this->code.push_back(pcode_exchange);
	  this->code.push_back(pcode_store_switch);
	} )]
      | (lit("printctr") > number) [push_back(boost::phoenix::ref(code), pcode_load_counter), push_back(boost::phoenix::ref(code), pcode_print_tos)]
      | (lit("incr") > number) [( [&] {std::copy(code_for_incr.begin(), code_for_incr.end(), back_inserter(this->code));} )]
      | (lit("decr") > number) [( [&] {std::copy(code_for_decr.begin(), code_for_decr.end(), back_inserter(this->code));} )]
      | (lit("swap") > objnum > objnum) [push_back(boost::phoenix::ref(code), pcode_swap_obj)]
      | (lit("move") > objnum > value) [push_back(boost::phoenix::ref(code), pcode_exchange), push_back(boost::phoenix::ref(code), pcode_move_obj)]

      | lit("test") [bind(&gac_lang_parser::test_fun, this)]
      | lit("test2") [bind(&gac_lang_parser::test_fun2, this, std::ref(code))]
      | (lit("lambdatest")  [ ([=] {std::cout << code.size();})])
      | lit("lambdatest2")  [( [&] {std::copy(code_for_available.begin(), code_for_available.end(), back_inserter(this->code));} )]
      ;
    action.name("action");
    
    on_error<fail>
      (
       expression
       , std::cout
       << val("Error! Expecting ")
       << _4                               // what failed?
       << val(" here: \"")
       << construct<std::string>(_3, _2)   // iterators to error-pos, end
       << val("\"")
       << std::endl
       );
  }
  
  rule<Iterator, boost::spirit::ascii::space_type> expression, bool_exprs, bool_expr, number, action, objnum, value, cond_objprobs, cond_visited, cond_markers, cond_eq, cond_lt, cond_gt, bool_expr_not, cond_leftvalue;
  pcode_array &code;
};

template <typename Grammar>
bool compile(Grammar const& calc, std::string const& expr) {
  std::string::const_iterator iter = expr.begin();
  std::string::const_iterator end = expr.end();
  bool r = phrase_parse(iter, end, calc, boost::spirit::ascii::space);
  
  if (r && iter == end) {
    return true;
  } else {
    std::cout << "Parsing failed, not parsed: '" << std::string(iter, end) << "'.\n";
    return false;
  }
}

pcode_array compile_gac_lang(const std::string &gac_code) {
  typedef std::string::const_iterator iterator_type;
  typedef gac_lang_parser<iterator_type> gac_lang_parser;

  pcode_array code;		//  Our VM code
  gac_lang_parser calc(code);	//  Our grammar

  if (!::compile(calc, gac_code)) {
    throw std::runtime_error("can not compile gac for: " + gac_code);
  }
  return code;
}


